ActiveX components can be executed remotely on another machine. Such a machine can be in the same or in a different room, in a different building, or even in a distant city. Thanks to the location transparency capabilities of COM, the client application always works as if the component executed locally. The only clue that the execution occurs remotely is that all calls to an object's properties and methods are much slower.
The portion of COM that deals with remote activation of a component is called Distributed COM, or DCOM for short. As I explained in the introduction to this chapter, DCOM was first introduced with Windows NT 4, and it should be made clear that Windows NT is the operating system of choice when using remote components because it's the only one that provides the necessary security in a multiuser environment. In a production-distributed environment, Windows 95 and 98 machines should work only as DCOM clients.
Visual Basic also supports another limited form of remote activation, Remote Automation. I won't cover this outdated technology in detail because it's slower and less reliable than DCOM. Nowadays, the only reason to use Remote Automation is for supporting 16-bit clients, which isn't possible in DCOM because it's a 32-bit only technology.
The remaining part of this chapter deals with remote ActiveX EXE components. You can also remotely execute ActiveX DLLs, either by using a standard surrogate process such as DllHost.Exe or by using Microsoft Transaction Server (MTS). The creation of components for MTS isn't covered in this book.
NOTE
While a Windows 95 or 98 machine isn't good as a DCOM server in a production environment, you can still use it as a server in the development stage. The solution isn't very efficient and has other drawbacks as well. For example, there's no launch capability, and the COM component must be already running to accept remote requests. For more information, see the Microsoft Knowledge Base article Q165101.
If you have created and tested an ActiveX EXE component on the local machine, turning it into a remote component doesn't require a recompilation. All you have to do is modify a few keys in the Registry of the local machine so that all requests for the objects are automatically redirected to another machine. In theory, you can deliver remote components also with the Professional Edition of Visual Basic. In practice, however, only the Enterprise Edition includes all the tools that let you deploy remote components easily.
The first thing you have to do when creating a component that you envision using as a remote server is tick the Remote Server Files check box in the Component tab of the Project Properties dialog box, as shown in Figure 16-23. If this option is enabled, Visual Basic creates two additional files when you compile the project. The files have the same name as the executable but different extensions: the .tlb type library and the .vbr registration file. These files are later used to register the component in a client workstation's Registry without physically installing the EXE file.
Figure 16-23. Preparing an ActiveX EXE component for remote execution.
The next step is the installation and registration of the component on the server workstation. You accomplish this by copying the EXE file on a local disk of the machine and then running it with the /REGSERVER switch on the command line. It's advisable that the EXE file be on a local drive instead of a networked drive because this setting raises fewer security issues. After the component has been registered, you can proceed to make it available to remote clients. You can choose from several tools to accomplish this.
The first and simplest tool of the group is the Remote Automation Connection Manager. This program has a dual purpose: You can employ it to make a local component available to remote clients using the commands on the Client Access tab, and you can run it on a client machine to modify the entry in the Registry so that all requests for a specific component's object are redirected to the server machine.
When the Remote Automation Connection Manager starts, it displays a list of all the components that are registered on the machine, as you can see in Figure 16-24. Using the option buttons on the Client Access tab, you can decide whether individual components should be available for remote activation.
Disallow All Remote Creates This setting makes all the registered components unavailable to remote clients.
Allow Remote Creates By Key You can make individual components available for remote activation; the state of each component depends on the Allow Remote Activation check box near the bottom border of the dialog box. This is a good choice under Windows 95 and 98 because these operating systems don't support ACLs. (See the next option.)
Figure 16-24. The Client Access tab of the Remote Automation Connection Manager.
Allow Remote Creates By ACL This setting is available only under Windows NT systems and lets you decide which users are granted or denied the permission to use the component currently highlighted in the leftmost list box.
Allow All Remote Creates All the registered components are available for remote activation; this setting should be used only under the development and debugging phase because it would make the server machine too vulnerable to attacks from malicious clients.
A quick way to test how your component works remotely is to install it on a client machine as if it were a local component (that is, by copying the EXE file and running it using the /REGSERVER switch). Launch the client application, and make sure that everything works as expected. This step is important to sort out problems that don't have to do with remote activation.
Now you can run the Remote Automation Connection Manager to modify the Registry so that all object requests will be redirected to the server. In this case, you have to use the Server Connection tab of the program, shown in Figure 16-25, where you select the Distributed COM setting and then make the object remote by using the Ctrl+R shortcut key or by using the Remote command in the Register menu or the pop-up menu that displays when you right-click on the window. To complete the registration procedure, you have to specify the name of the server machine where the object will be instantiated. When you're working with DCOM, this utility doesn't allow you to specify a network protocol or an authentication level.
Run the client again and ensure that everything is working; if you didn't make any errors, the object will now be instantiated on the server. You might not see the remote component on the server's monitor, but you can check that it appears in the list of processes when the client makes the request.
Figure 16-25. The Server Connection tab of the Remote Automation Connection Manager.
Visual Basic 6 improves the CreateObject function with the support of a second optional argument, which gives the client the ability to instantiate the component on a specific remote workstation, as in the following code:
Set x = CreateObject("RemoteSvr.TestClass", "ServerNT") |
This feature makes it possible to write smarter clients that can implement a fault-tolerant mechanism and instantiate an object on an alternate workstation if the machine the Registry points to is currently unavailable. Thanks to this capability, a distributed application can also implement a sophisticated load-balancing algorithm so that heavy-duty components run on the machines that are currently idle.
While the Remote Automation Connection Manager is fine for testing that the client application correctly connects to the component, its interactive nature gets in the way when it's time to actually deploy the application on multiple workstations. The solution to this problem lies in the Client Configuration utility, CliReg32.Exe. You can find this program in the Common\Tools\CliReg directory in your Visual Studio main directory, and you should ensure that it's in the client installation package.
You (or your installation routine) have to run the CliReg32 program and pass it the name of the VBR file that was produced when the component was compiled. The VBR file is nothing but a REG file with a different extension and a different header. The CliReg32 utility reads this file, customizes it using the settings found on the command line, and finally adds all the relevant keys to the Registry. Its syntax is the following:
CliReg32 <vbrfile> <options> |
where all the available options are listed in Table 16-3.
Table 16-3. The options for the CliReg32 utility.
Option | Description |
---|---|
-d | Use DCOM instead of Remote Automation. |
-t <typelib> | Specify a type library file. |
-a | Specify an authentication security level. (n can be in the range 0 through 6, corresponding to the values listed in Table 16-4.) |
-s <address> | Specify a network address. |
-p <protocol> | Specify a network protocol. |
-u | Uninstall the VBR file. |
-l | Log error information to file CLIREG.LOG. |
-q | Suppress the dialog box; if you omit this option, CliReg32 displays a dialog box for letting the user enter missing values. |
-nologo | Suppress the copyright dialog box. |
-h or -? | Show this list of options. |
DCOMCNFG is the main utility that you use to configure DCOM. When you run it for the first time, it quickly scans all the registered components in the system and prepares them for execution as remote components by adding their identifiers under the HKEY_CLASSES_ROOT\AppID key in the Registry. This portion of the Registry gathers all the information about component security. While a few components register themselves under this key, this doesn't happen with all the components authored in Visual Basic. Note that DCOMCNFG displays one AppID for each ActiveX server, even if the server exposes multiple classes.
DCOMCNFG also activates or deactivates DCOM as a whole. In the Default Properties tab shown in Figure 16-26, you should tick the Enable Distributed COM On This Computer check box. This option must be selected to have the current machine work either as a server or as a client in a DCOM connection. You might need to reboot to enforce a new setting for this option.
DCOMCNFG enables you to set both the DCOM default security settings and the security settings for each particular component. This sort of security is named declarative security, and it can be assigned from outside the component itself. DCOM also supports programmatic security, which enables the programmer to dynamically modify the security settings of the component at run time, and even set a different security level on a per-method basis. Unfortunately, programmatic security is beyond the capabilities of Visual Basic.
Figure 16-26. The Default Properties tab of the DCOMCNFG utility.
The Default Authentication Level option tells DCOM how it should check that data sent to the component is actually coming from the client. Higher security levels protect the server from data tampering, but at the same time they slow down the communication with its clients. The authentication levels supported by DCOM are listed in Table 16-4.
Table 16-4. DCOM authentication levels.
Value | Level | Description |
---|---|---|
0 | Default | Corresponds to Connect authentication. |
1 | None | DCOM doesn't authenticate data in any way. |
2 | Connect | DCOM authenticates the client only when it first connects to the server. |
3 | Call | DCOM authenticates the client at the beginning of each call to a method or property. |
4 | Packet | DCOM authenticates each packet of data coming from the client. |
5 | Packet integrity | Similar to previous level, but a checksum mechanism ensures that data hasn't been altered on the way from the client to the server. |
6 | Packet privacy | Similar to previous level, but data is also encrypted to ensure that it isn't read by unauthorized programs. |
The impersonation level defines what the component can do on behalf of its clients. The lower the impersonation level is, the more protected the client is from misbehaving components. The Default Impersonation Level field determines the impersonation level assigned to all components that don't override this setting with a different value. DCOM supports four impersonation levels, which are summarized in Table 16-5.
Table 16-5. DCOM impersonation levels. The default setting is Identify.
Value | Level | Description |
---|---|---|
1 | Anonymous | The server doesn't know anything about the client and therefore can't impersonate it. (This level isn't currently supported and is automatically promoted to Identify.) |
2 | Identify | The server has enough information about the client to impersonate it in ACL checking but can't access system objects as the client. |
3 | Impersonate | The server can impersonate the client while acting on its behalf. |
4 | Delegate | The server can impersonate the client when calling other servers on the behalf of the client. (Supported only in Windows 2000.) |
In the Default Security tab, you select the list of users that are enabled to run or use all the components that don't provide a customized list of authorized users. To use a component, a user must be included in the Access Permission list, while to launch the component the user must be included in the Launch Permission list. Both of them are Windows NT Access Control Lists, as shown in Figure 16-27. You shouldn't modify these lists; instead, you should modify the individual ACLs associated with each component, as I'll explain in a moment. It's important that the SYSTEM user be included in both the access and launch lists; otherwise, the component can't be launched at all.
The Default Configuration Permission list includes all the users who are allowed to change the security settings of all the components that don't provide a customized list of authorized users. You shouldn't modify these settings because restricting the access to the Registry might cause problems to components written in Visual Basic, which register themselves each time they're launched.
Figure 16-27. The Default Security tab of the DCOMCNFG utility.
If you switch to the Applications tab of the DCOMCNFG utility and double-click on a component's name, another window appears; this window is where you modify the security settings and other properties of that particular component. For example, in the General tab, you can set a custom Authentication level for the component, whereas in the Security tab, you define exactly which users can access or launch this component or can modify its configuration permissions.
The most interesting settings are in the Identity tab, as shown in Figure 16-28.
Figure 16-28. The Identity tab of the DCOMCNFG utility for a specific component.
Here you decide under which user account the component will run. DCOM provides the following three choices:
Run as The Interactive User This option assigns the component the security credential of the user who is currently logged on the machine. This generally isn't a wise choice in a real distributed system because the permissions granted to the component vary depending on who is logged on. If no user has logged on, the component can't even run. In practice, this option makes sense only when you're testing the component.
All components that run in an account different from the interactive user's account execute in a noninteractive window station and aren't visible on the desktop. If you want to see the output of a component, it must run under the identity of the interactive user. For the same reason, unless you're 100 percent sure that your component will be run under the interactive user account, you should compile it using the Unattended Execution option. If you don't and an error occurs, the component will wait forever because no user can click on its error message box.
Run as The Launching User This option assigns the security credentials of the user who is running the client application. This option usually isn't a good choice because different clients instantiate remote objects that must run under different credentials, and this is possible only by running multiple instances of the component. In this case, the component acts more or less as a SingleUse component even if it was compiled with the MultiUse attribute. Moreover, if a component is running under the account of a remote client, the component won't be able to make calls to components that run on another machine, at least on Windows NT 4 (which doesn't support the Delegate impersonation level).
Run as This User This option lets you assign a specific user's security credentials to the component. In a production environment, this is often the best choice because only one instance of a MultiUse component will be created. In practice, the best thing to do is create a new user just for this purpose, give it proper access rights to system resources, and then let the component run with this new user's credentials. In this way, you can modify the access rights of one or more components by simply changing the rights of this fictitious user.
It's important that this user be assigned the permission to log on as a batch job, otherwise the logon process that invisibly starts when the component is launched will fail. DCOMCNFG automatically assigns this right to the user in the Run As This User field, and you only have to avoid accidentally revoking this right from within the Windows NT User Manager utility.
An area in which you can greatly improve the performance of your remote components is event notification. While your ordinary event notifications work under DCOM (but not under Remote Automation), they're so inefficient that I strongly discourage you from using them. Instead, you should implement a callback technique.
The callback mechanism works as follows: When the client calls a lengthy method of the component, it passes a reference to an object defined in the client itself, and the component stores this reference in a local variable. This variable is then used when the component needs to call back the client to inform it that something has occurred.
Callback techniques have been available to Visual Basic programmers since version 4 of the language. But only late-binding callbacks were possible in Visual Basic 4. Let me describe a concrete example of the callback technique. Say that you have created a generic reusable report printing engine: Any client application can instantiate it and start a print job. The server then calls back the client when the job has been completed or when an error occurs.
In this scenario, the print server doesn't know at compile time the type of the object passed by the client application because different types of clients expose different classes. The component can store a reference to the client only in a variable declared using As Object, which means that the notification occurs through late binding. The client and the component must agree on the name and the syntax of a method in the class used for callback. Any syntax error in the client or in the server will manifest itself only at run time. As you know, late binding is also less efficient than early binding, so you should avoid it if possible.
In Visual Basic 5 and 6, the Implements keyword allows you to enforce a stricter contract between the client and the component. The component includes a PublicNotCreatable class that defines the callback interface, and any client that wants to receive callback notifications from the server has to expose a PublicNotCreatable class that implements that interface. The component can therefore store a reference to such an object in a specific variable, and all the notifications use early binding.
On the companion CD-ROM, you can find the complete source code of a multithreaded component that implements a callback mechanism to communicate with its clients. This component performs a (simulated) printing job and tells the client how the job progresses and when it completes. The component includes the CPrinterCBK PublicNotCreatable class that defines the callback interface:
' The CPrinterCBK class module. Sub Complete(ErrCode As Long) ' End Sub Sub Progress(percent As Integer) ' End Sub |
This is the source code of the CPrinter class, which simulates the actual printing:
' The CPrinter class module. Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Dim SaveCBK As CPrinterCBK Dim frmTimer As frmTimer Sub StartPrintJob(Filename As String, cbk As CPrinterCBK) ' Save the callback object for later. Set SaveCBK = cbk ' Activate the timer that will restart this thread. Set frmTimer = New frmTimer With frmTimer Set .Owner = Me .Timer1.Interval = 100 .Timer1.Enabled = True End With End Sub Friend Sub StartIt() Dim totalTime As Single, percent As Integer Dim t As Single, startTime As Single ' This code is executed when the timer fires. ' Unload the form, and destroy it completely. Unload frmTimer Set frmTimer = Nothing ' Simulate the printing process. totalTime = Rnd * 10 + 5 startTime = Timer Do ' Inform the client that something has happened. percent = ((Timer - startTime) / totalTime) * 100 SaveCBK.Progress percent ' In this demo, just go to sleep for one second. Sleep 1000 Loop Until Timer - startTime > totalTime ' Inform the client that the process has been completed. SaveCBK.Complete 0 ' IMPORTANT: destroy the reference to the client ' so that it won't be kept alive forever. Set SaveCBK = Nothing End Sub |
The component also includes a frmTimer form with a Timer control on it; the only purpose of this form is to wake the component a few milliseconds after it has returned the control to its client from the StartPrintJob method:
' The frmTimer form module. Public Owner As CPrinter Private Sub Timer1_Timer() ' This procedure is executed only once. Timer1.Interval = 0 Timer1.Enabled = False ' Yield to the companion CPrinter instance. Owner.StartIt End Sub |
On the CD-ROM, you can also find a client application that uses this component and that performs a CPU intensive task (finding prime numbers) while waiting for the simulated printing job to complete. If you don't have a network of computers, you can run multiple instances of this application and see that they can multitask without affecting one another, as shown in Figure 16-29.
Figure 16-29. Multiple clients that communicate with a sample multithreading component through callbacks.
The client application includes a PublicNotCreatable callback class module, which is passed to the server component:
' The CallbackCls class module. Implements PrintServer.CPrinterCBK ' This class directly references controls on the main form. Private Sub CPrinterCBK_Complete(ErrCode As Long) frmClient.lblStatus = "Completed" frmClient.cmdStart.Enabled = True End Sub Private Sub CPrinterCBK_Progress(percent As Integer) frmClient.lblStatus = "Printing " & percent & "%" End Sub |
The only portion of the main form in the client application that's relevant in this context is the point at which it instantiates the component and passes it a reference to a CallbackCls object:
Private Sub cmdStart_Click() Dim prn As PrintServer.CPrinter ' Ask the CPrinter server to spool a fictitious file. Set prn = New PrintServer.CPrinter prn.StartPrintJob "a dummy filename", New CallbackCls End Sub |
Note that the client doesn't need to store a reference to the CallbackCls object in a local variable because this object is kept alive by the server component. Moreover, the CallbackCls class module can work to implement a callback mechanism from multiple servers, each one defining its own callback interface. In this case, the class has to include multiple Implements statements, one for each supported callback interface.
The callback mechanism is undoubtedly more complex than a notification method based on events. For one thing, the client must be an ActiveX EXE itself to expose a public COM object, and the component must have sufficient rights to call a method in this object. On the other hand, callbacks have many advantages over events:
Note that the callback technique described here isn't just for remote components and can be effectively used with local components as well.
Now you know everything you need to know to create great in-process, local, and remote components. But Visual Basic 6 also lets you create another type of component, namely ActiveX controls. They're covered in the next chapter.